﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace Function_Analyzer.MathUtil
{
    public class DaoHam
    {
        const int MIN_FUNC_LENGTH = 2;
        const int MAX_FUNC_LENGTH = 5;

        public static string GetDerivative(string func)
        {
            string result = Derivative(EditedEqu(func, true));
            return result;
        }

        public static bool IsValid(string exp, bool isvalidX, bool isvalidY, bool isvalidT)
        {
            exp = exp.ToLower();

            //xu ly dau tri tuyet doi
            while (exp.Contains("|"))
            {
                int j = exp.IndexOf("|");
                if (j == 0)
                {
                    exp = "abs(" + exp.Substring(j + 1);
                }
                else
                {
                    char c = exp[j - 1]; //Substring(j - 1, 1).ToCharArray()[0];
                    if (c == '(' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^')
                    {
                        exp = exp.Substring(0, j) + "abs(" + exp.Substring(j + 1);
                    }
                    else
                    {
                        exp = exp.Substring(0, j) + ")" + exp.Substring(j + 1);
                    }
                }
            }

            char[] expArray = new char[exp.Length];
            int numElements = 0, numLB = 0, numRB = 0;
            int i = 0;
            while (i < exp.Length)
            {
            continous:
                if (exp[i] == '(')
                {
                    expArray[numElements] = '(';
                    numElements++;
                    numLB++;
                    i++;
                    continue;
                }
                if (exp[i] == ')')
                {
                    expArray[numElements] = ')';
                    numElements++;
                    numRB++;
                    if (numLB < numRB) return false;
                    i++;
                    continue;
                }
                if (Char.IsNumber(exp[i]))
                {
                    while(true)
                    {
                        if (i < exp.Length && Char.IsNumber(exp[i])) i++;
                        else break;
                    } 
                    expArray[numElements]='n';  //num
                    numElements++;
                    continue;
                }
                if (Util.IsOper(exp[i].ToString()))
                {
                    expArray[numElements] = 'o'; //operation
                    numElements++;
                    i++;
                    continue;
                }
                for (int j = MIN_FUNC_LENGTH; j <= MAX_FUNC_LENGTH; j++)
                {
                    if (exp.Length >= i + j + 2 && Util.IsFunc(exp.Substring(i, j)) && exp[i + j] == '(')
                    {
                        expArray[numElements] = 'f'; //func
                        numElements++;
                        i+=j;
                        goto continous;
                    }
                }
                if (((exp[i] == 'x' && isvalidX) || (exp[i] == 'y' && isvalidY) || (exp[i] == 't' && isvalidT)) && (i == exp.Length - 1 || (i < exp.Length - 1 && !Char.IsLetter(exp[i + 1]))))
                {
                    expArray[numElements]='n';
                    numElements++;
                    i++;
                    continue;
                }
                if (exp[i] == 'e' && (i == exp.Length - 1 || (i < exp.Length - 1 && !Char.IsLetter(exp[i + 1]))))
                {
                    expArray[numElements] = 'n';
                    numElements++;
                    i++;
                    continue;
                }
                return false;
            }

            if (numElements == 0 || numLB!=numRB) return false;
            for (i = 0; i < numElements; i++)
            {
                switch(expArray[i]){
                    case 'n':
                        if (i > 0 && (expArray[i - 1] == ')' || expArray[i - 1] == 'f')) return false;
                        if (i < numElements - 1 && (expArray[i + 1] == '(' || expArray[i + 1] == 'f' || expArray[i + 1] == 'n')) return false;
                        break;
                    case 'f':
                        if (i > 0 && (expArray[i - 1] == ')' || expArray[i - 1] == 'f' || expArray[i-1]=='n')) return false;
                        if (i < numElements - 1 && (expArray[i + 1] == 'n' || expArray[i + 1] == 'o' || expArray[i + 1] == 'f' || expArray[i+1]==')')) return false;
                        if (i == numElements - 1) return false;
                        break;
                    case 'o':
                        if (i > 0 && (expArray[i - 1] == 'f')) return false;
                        if (i < numElements - 1 && (expArray[i + 1] == ')')) return false;
                        if (i == numElements - 1) return false;
                        break;
                    case '(':
                        if (i > 0 && (expArray[i - 1] == ')' || expArray[i-1]=='n')) return false;
                        if (i < numElements - 1 && (expArray[i + 1] == ')')) return false;
                        if (i == numElements - 1) return false;
                        break;
                    case ')':
                        if (i > 0 && (expArray[i - 1] == '(' || expArray[i - 1] == 'f' || expArray[i-1]=='o')) return false;
                        if (i == 0) return false;
                        if (i < numElements - 1 && (expArray[i + 1] == 'n' || expArray[i + 1] == '(' || expArray[i + 1] == 'f')) return false;
                        break;
                }
            }

            char[] opers={'+','-','*','/','^'};
            for (i = 0; i < opers.Length; i++)
            {
                for (int j = 0; j < opers.Length; j++)
                {
                    if (exp.Contains(opers[i].ToString() + opers[j].ToString())) return false;
                }
            }
            if (exp.Length > 0 && (exp[0] == '*' || exp[0] == '/' || exp[0] == '^')) return false;
            return true;
        }

        public static string EditedEqu(string exp)
        {
            return EditedEqu(exp, false);
        }

        public static string EditedEqu(string exp, bool toDerive)
        {
            exp = exp.ToLower().Replace(',', '.').Replace(" ", "");

            //xu ly dau tri tuyet doi
            while (exp.Contains("|"))
            {
                int j = exp.IndexOf("|");
                if (j == 0)
                {
                    exp = "abs(" + exp.Substring(j + 1);
                }
                else
                {
                    char c = exp[j-1]; //Substring(j - 1, 1).ToCharArray()[0];
                    if (c == '(' || c == '+' || c == '-' || c == '*' || c == '/' || c == '^')
                    {
                        exp = exp.Substring(0, j) + "abs(" + exp.Substring(j + 1);
                    }
                    else
                    {
                        exp = exp.Substring(0, j) + ")" + exp.Substring(j + 1);
                    }
                }
            }

            //todo: dinh nghia tham so

            //
            exp = exp.Replace("++", "+");
            exp = exp.Replace("--", "+");
            exp = exp.Replace("+-", "-");
            exp = exp.Replace("-+", "-");
            exp = exp.Replace(" ", "-");

            //
            int i = 1;
            while (i < exp.Length)
            {
                if (!toDerive)
                {
                    if (exp[i] == '-' && exp[i - 1] != 'e' && exp[i - 1] != '*' && exp[i - 1] != '/' && exp[i - 1] != '^' && exp[i + 1] != '1')
                    {
                        exp = Util.ReplaceOneTime(exp, i, 1, "+(-1)*");
                        continue;
                    }
                }
                if (exp[i] == 'x' && (Char.IsNumber(exp[i - 1]) || exp[i - 1] == ')' || exp[i - 1] == 'y'))
                {
                    exp = exp.Insert(i, "*");
                    continue;
                }
                if (exp[i] == 'y' && (Char.IsNumber(exp[i - 1]) || exp[i - 1] == ')' || exp[i - 1] == 'x'))
                {
                    exp = exp.Insert(i, "*");
                    continue;
                }
                if (Char.IsLetter(exp[i]) && (Char.IsNumber(exp[i - 1]) || exp[i - 1] == ')' || exp[i - 1] == 'x' || exp[i - 1] == 'y'))
                {
                    if (i > 1)
                    {
                        if (exp[i - 2] == 'e')
                        {
                            break;
                        }
                    }
                    exp = exp.Insert(i, "*");
                    continue;
                }
                if (exp[i] == '(' && (Char.IsNumber(exp[i - 1]) || exp[i - 1] == 'x' || exp[i - 1] == 'y' || exp[i - 1] == ')'))
                {
                    if (i > 2)
                    {
                        if (exp.Substring(i - 3, 2) == "fi")
                        {
                            break;
                        }
                    }
                    exp = exp.Insert(i, "*");
                    if (exp[i] == '-' && exp[i - 1] != 'e')
                    {
                        exp = Util.ReplaceOneTime(exp, i, 1, "(-1)*");
                    }
                    continue;
                }
                i++;
            }

            if (!toDerive && exp[0] == '-')
            {
                exp = Util.ReplaceOneTime(exp, 0, 1, "(-1)*");
            }
            return exp;
        }

        private static string DerivativeN(string exp, int n)
        {
            if (n <= 0) return exp;
            else
            {
                return GetDerivative(DerivativeN(exp, n - 1));
            }
        }

        private static string Derivative(string exp)
        {
            return Derivative(exp, false);
        }

        private static string Derivative(string exp, bool inBrackets)
        {
            if (exp=="x") return "1";
            if (!exp.Contains("x")) return "0";

            string rs="";
            int depth = 0;
            for (int i = 0; i < exp.Length; i++)
            {
                if (exp[i] == '(') depth++;
                if (exp[i] == ')') depth--;
                if (depth == 0)
                {
                    string firstF = exp.Substring(0, i);
                    string secondF = exp.Substring(i + 1);

                    if (exp[i] == '+')
                    {
                        if (firstF.Contains("x")) rs=Derivative(firstF);
                        if (firstF.Contains("x") && secondF.Contains("x")) rs+="+";
                        if (secondF.Contains("x")) rs+= Derivative(secondF);
                        if (rs == "") rs = "0";
                        if (!rs.Contains("x")) rs = MathCal.Val(rs).ToString();
                        return rs;
                    }

                    if (exp[i] == '-')
                    {
                        int depth1 = 0;
                        int endOfFirstPart = exp.Length - 1;
                        if (exp[i + 1] != '+' && exp[i + 1] != '-')
                        {
                            for (int j = i + 1; j < exp.Length; j++)
                            {
                                if (exp[j] == '(') depth1++;
                                if (exp[j] == ')') depth1--;
                                if (depth1 == 0 && (exp[j] == '+' || exp[j] == '-'))
                                {
                                    endOfFirstPart = j - 1;
                                }
                            }
                        }
                        if (firstF.Contains("x")) rs = Derivative(firstF);
                        if (exp.Substring(i + 1, endOfFirstPart - i).Contains("x"))
                        {
                            if (!IsInZeroDepth(secondF, "+") && !IsInZeroDepth(secondF, "-"))
                            {
                                rs += "-" + PutBrackets(Derivative(secondF), true);
                            }
                            else
                            {
                                rs += "-" + Derivative(secondF);
                            }
                        }
                        else if(endOfFirstPart!=exp.Length-1)
                        {
                            if (rs != "") rs += "+";
                            rs += Derivative(exp.Substring(endOfFirstPart+1));
                        }
                        if (rs == "") rs = "0";
                        if (!rs.Contains("x")) rs = MathCal.Val(rs).ToString();
                        return rs;
                    }
                }
            }

            depth = 0;
            for (int i = exp.Length - 1; i >= 0; i--)
            {
                if (exp[i] == '(') depth++;
                if (exp[i] == ')') depth--;
                if (depth == 0)
                {
                    string firstF = exp.Substring(0, i);
                    string secondF = exp.Substring(i + 1);

                    if (exp[i] == '*')
                    {
                        if (firstF != "0" && secondF.Contains("x")) rs = Delete1(firstF + "*" + PutBrackets(Derivative(secondF), true));
                        if (rs != "" && secondF != "0" && firstF.Contains("x")) rs += "+";
                        if (secondF != "0" && firstF.Contains("x")) rs += Delete1(secondF+"*"+PutBrackets(Derivative(firstF),true));
                        if (rs == "") rs = "0";
                        if (!rs.Contains("x")) rs = MathCal.Val(rs).ToString();
                        return rs;
                    }
                    if (exp[i] == '/')
                    {
                        if (secondF != "0" && firstF.Contains("x")) rs = Delete1(secondF + "*" + PutBrackets(Derivative(firstF)));
                        if (firstF != "0" && secondF.Contains("x")) rs += "-" + Delete1(firstF + "*" + PutBrackets(Derivative(secondF)));
                        if (rs != "") rs = PutBrackets(rs)+"/"+secondF+"^2";
                        if (rs == "") rs = "0";
                        if (!rs.Contains("x")) rs = MathCal.Val(rs).ToString();
                        return rs;
                    }
                }
            }

            depth = 0;
            for (int i = exp.Length - 1; i >= 0; i--)
            {
                if (exp[i] == '(') depth++;
                if (exp[i] == ')') depth--;
                if (depth == 0)
                {
                    string firstF = exp.Substring(0, i);
                    string secondF = exp.Substring(i + 1);

                    if (exp[i] == '^' && !secondF.Contains("x")) {
                        return PutBrackets(MathCal.Val(secondF).ToString(), true)+"*"+firstF+DeletePower("^"+PutBrackets((MathCal.Val(secondF)-1).ToString(), true))+Delete1("*"+Derivative(firstF));
                    }
                    if (exp[i] == '^')
                    {
                        rs=exp+"*("+Delete1(Derivative(secondF)+"*ln("+firstF+")");
                        if (Derivative(firstF) != "0") 
                        {
                            rs += "+" + Delete1(secondF + "*" + Derivative(firstF)) + "/" + firstF;
                        }
                        rs += ")";
                        if (!rs.Contains("x")) rs = MathCal.Val(rs).ToString();
                        return rs;
                    }
                }
            }

            for (int i = MAX_FUNC_LENGTH; i >= MIN_FUNC_LENGTH; i--)
            {
                if (Util.IsFunc(exp.Substring(0, i)))
                {
                    string insideF = exp.Substring(i);
                    string insideF2 = PutBrackets(insideF.Substring(1, insideF.Length - 2));
                    string insDer = Derivative(insideF.Substring(1, insideF.Length - 2));
                    
                    switch (exp.Substring(0, i))
                    {
                        case "sin": rs = "cos" + insideF; break;
                        case "cos": rs = "-sin" + insideF; break;
                        case "tan": rs = "sec" + insideF + "^2"; break;
                        case "cot": rs = "-csc" + insideF + "^2"; break;
                        case "sec": rs = "sec" + insideF + "*tan" + insideF; break;
                        case "csc": rs = "-csc" + insideF + "*cot" + insideF; break;
                        case "asin": rs = "1/sqrt(1-" + insideF2 + "^2)"; break;
                        case "acos": rs = "-1/sqrt(1-" + insideF2 + "^2)"; break;
                        case "atan": rs = "1/(1+" + insideF2 + "^2)"; break;
                        case "asec": rs = "1/(abs" + insideF + "*sqrt(" + insideF2 + "^2-1))"; break;
                        case "acsc": rs = "-1/(abs" + insideF + "*sqrt(" + insideF2 + "^2-1))"; break;
                        case "acot": rs = "-1/(1+" + insideF2 + "^2)"; break;
                        case "sinh": rs = "cosh" + insideF; break;
                        case "cosh": rs = "sinh" + insideF; break;
                        case "tanh": rs = "1/cosh" + insideF + "^2"; break;
                        case "sech": rs = "-sech" + insideF + "*tanh" + insideF; break;
                        case "csch": rs = "-csch" + insideF + "*coth" + insideF; break;
                        case "coth": rs = "-csch" + insideF + "^2"; break;
                        case "asinh": rs = "1/sqrt(" + insideF2 + "+1)"; break;
                        case "acosh": rs = "1/sqrt(" + insideF2 + "-1)"; break;
                        case "atanh": rs = "1/(1-" + insideF2 + "^2)"; break;
                        case "asech": rs = "-1/(abs" + insideF + "*sqrt(1-" + insideF2 + "^2))"; break;
                        case "acsch": rs = "-1/(abs" + insideF + "*sqrt(1+" + insideF2 + "^2))"; break;
                        case "acoth": rs = "1/(1-" + insideF2 + "^2)"; break;
                        case "log":
                        case "ln": rs = "1/" + insideF2; break;
                        case "lg": rs = "1/(" + insideF2 + "*ln(10))"; break;
                        case "abs": rs = "sgn" + insideF; break;
                        case "sgn":
                        case "int": rs = "0"; break;
                        case "sqr":
                        case "sqrt": rs = "0.5*" + insideF + "^(-0.5)"; break;
                    }
                    if (insDer != "1" && insDer != "0" && rs != "0") rs += "*" + PutBrackets(insDer);
                    if (rs == "") rs = "0";
                    return rs;
                }
            }
            if (exp[0] == '(' && exp[exp.Length - 1] == ')')
            {
                if (!inBrackets) rs = "(";
                rs += Derivative(exp.Substring(2, exp.Length - 2));
                if (!inBrackets) rs += ")";
            }

            return rs;
        }

        private static string PutBrackets(string exp)
        {
            return PutBrackets(exp, false);
        }

        private static string PutBrackets(string exp, bool isPlusMinus)
        {
            int start = 0;
            for (int i = MIN_FUNC_LENGTH; i <= MAX_FUNC_LENGTH; i++)
            {
                if (exp.Length >= i + 2 && Util.IsFunc(exp.Substring(0, i)))
                {
                    start = i;
                }
            }
            
            if (exp[start] == '(' && exp[exp.Length - 1] == ')')
            {
                int depth = 0;
                bool needBrck = false;
                for (int i = start + 1; i < exp.Length - 1; i++)
                {
                    if (exp[i] == '(') depth++;
                    if (exp[i] == ')') depth--;
                    if (depth < 0)
                    {
                        needBrck = true;
                        break;
                    }
                }
                if (!needBrck) return exp;
            }
            if ((Util.IsNumericStr(exp) && MathCal.Val(exp) >= 0) || exp == "x" || (isPlusMinus && !IsInZeroDepth(exp, "+") && !IsInZeroDepth(exp, "-"))) return exp;
            return "(" + exp + ")";
        }

        private static bool IsInZeroDepth(string st1, string st2)
        {
            int depth = 0;
            for (int i = 0; i < st1.Length; i++)
            {
                if (st1[i] == '(') depth++;
                if (st1[i] == ')') depth--;
                if (depth == 0 && st1[i].ToString() == st2) return true;
            }
            return false;
        }

        private static string Delete1(string exp)
        {
            int depth = 0;
            for (int i = 0; i < exp.Length; i++)
            {
                if (exp[i] == '(') depth++;
                if (exp[i] == ')') depth--;
                if (depth == 0)
                {
                    string firstF1 = exp.Substring(0, i);
                    string secondF1 = exp.Substring(i + 1);
                    if (exp[i] == '*')
                    {
                        if (firstF1 == "1") return exp.Substring(i + 1);
                        if (secondF1 == "1") return exp.Substring(0, i);
                    }
                }
            }
            return exp;
        }

        private static string DeletePower(string exp)
        {
            if (exp == "^1" || exp == "^ 1") return "";
            return exp;
        }
    }
}
